/** time.d - Date & time utilities
 
Authors:
	Wei Li (oldrev@gmail.com)  - D version

License:	MIT
*/


// This module is port from Mono Project
// System.DateTime.cs
//
// author:
//   Marcel Narings (marcel@narings.nl)
//   Martin Baulig (martin@gnome.org)
//   Atsushi Enomoto (atsushi@ximian.com)
//
//   (C) 2001 Marcel Narings
// Copyright (C) 2004-2006 Novell, Inc (http://www.novell.com)
//
//
// System.TimeSpan.cs
//
// Authors:
//   Duco Fijma (duco@lorentz.xs4all.nl)
//   Andreas Nahr (ClassDevelopment@A-SoftTech.com)
//   Sebastien Pouliot  <sebastien@ximian.com>
//
// (C) 2001 Duco Fijma
// (C) 2004 Andreas Nahr
// Copyright (C) 2004 Novell (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

module dotmars.base.time;

import dotmars.base.stdexcept;
import dotmars.base.math;
import dotmars.base.convert;
import dotmars.globalization.time;

version(Posix) {
	import dotmars.platform.posix.time;
	import dotmars.platform.posix.sys.time;
}

version(Windows) {
	import dotmars.platform.base;
}

public enum DateTimeKind : uint
{
	Unspecified,
	Utc,
	Local
}

public enum DayOfWeek 
{
	Sunday,
	Monday,
	Tuesday,
	Wednesday,
	Thursday,
	Friday,
	Saturday
}

////////////////////////////////////////////////////////////////////////////////


public abstract class TimeZone
{
	private static TimeZone m_currentTimeZone;

	public static this() {
		m_currentTimeZone  = null; //= new CurrentSystemTimeZone (DateTime.getUtcNow ());
	}
	
	public this() {
	}

	public static TimeZone currentTimeZone() {
		return m_currentTimeZone;
	}

	public abstract char[] daylightName();
	public abstract char[] standardName(); 
	public abstract DaylightTime getDaylightChanges (int year);
	public abstract TimeSpan getUtcOffset (DateTime time);

	public bool isDaylightSavingTime (DateTime time) {
		return isDaylightSavingTime (time, getDaylightChanges (time.year));
	}

	public static bool isDaylightSavingTime (DateTime time, DaylightTime daylightTimes)
	in {
		assert(daylightTimes !is null);
	}
	body{

		// If Start == End, then DST is off
		if (daylightTimes.start.ticks == daylightTimes.end.ticks)
			return false;

		//We are in the northern hemisphere.
		if (daylightTimes.start.ticks < daylightTimes.end.ticks) {
			if (daylightTimes.start.ticks < time.ticks && daylightTimes.end.ticks > time.ticks)
				return true; // time lies between Start and End

		}
		else {  // We are in the southern hemisphere.
			if (time.year == daylightTimes.start.year && time.year == daylightTimes.end.year)
				if (time.ticks < daylightTimes.end.ticks || time.ticks > daylightTimes.start.ticks)
					return true; // time is less than End OR more than Start 
		}

		return false;
	}

	public DateTime toLocalTime (DateTime time)
	{

		TimeSpan utcOffset = getUtcOffset (time);
		if (utcOffset.ticks > 0) {
			if (DateTime.max - utcOffset < time)
			return DateTime.max;
		} else if (utcOffset.ticks < 0) {
			if (time.ticks + utcOffset.ticks < DateTime.min.ticks)
				return DateTime.min;
		}

		DateTime local = time.opAdd (utcOffset);
		DaylightTime dlt = getDaylightChanges (time.year);
		if (dlt.delta.ticks == 0)
		return local;
		// FIXME: check all of the combination of
		//	- basis: local-based or UTC-based
		//	- hemisphere: Northern or Southern
		//	- offset: positive or negative

		// PST should work fine here.
		if (local < dlt.end && dlt.end.opSub (dlt.delta) <= local)
		return local;
		if (local >= dlt.start && dlt.start.opAdd (dlt.delta) > local)
		return local.opSub (dlt.delta);

		TimeSpan localOffset = getUtcOffset (local);
		return time.opAdd (localOffset);
	}

	public DateTime toUniversalTime (DateTime time)
	{

		TimeSpan offset = getUtcOffset (time);

		if (offset.ticks < 0) {
			if (DateTime.max + offset < time)
			return DateTime.max;
		} else if (offset.ticks > 0) {
			if (DateTime.min + offset > time)
			return DateTime.min;
		}

		return DateTime (time.ticks - offset.ticks);
	}

	TimeSpan getLocalTimeDiff (DateTime time)
	{
		return getLocalTimeDiff (time, getUtcOffset (time));
	}

	TimeSpan getLocalTimeDiff (DateTime time, TimeSpan utc_offset)
	{
		DaylightTime dlt = getDaylightChanges (time.year);

		if (dlt.delta.ticks == 0)
			return utc_offset;

		DateTime local = time.opAdd (utc_offset);
		if (local < dlt.end && dlt.end.opSub (dlt.delta) <= local)
			return utc_offset;

		if (local >= dlt.start && dlt.start.opAdd (dlt.delta) > local)
			return utc_offset - dlt.delta;

		return getUtcOffset (local);
	}
}

////////////////////////////////////////////////////////////////////////////////




////////////////////////////////////////////////////////////////////////////////

public struct TimeSpan
{
	public const long TicksPerDay = 864000000000L;
	public const long TicksPerHour = 36000000000L;
	public const long TicksPerMillisecond = 10000L;
	public const long TicksPerMinute = 600000000L;
	public const long TicksPerSecond = 10000000L;

	private long m_ticks = 0L;

	// 用 max/min 而不是原来的 MaxValue MinValue 是为了与内置类型高度保持一致
	// 这样有利于泛型算法、容器的使用

	public const TimeSpan min = { m_ticks: long.min };
	public const TimeSpan max = { m_ticks: long.max };
	public const TimeSpan zero = { m_ticks: 0L };
	
	public static TimeSpan opCall (long value)
	{
		TimeSpan ts;
		ts.m_ticks = value;
		return ts;
	}

	public static TimeSpan opCall (int hours, int minutes, int seconds)
	{
		TimeSpan ts;
		ts.m_ticks = calculateTicks (0, hours, minutes, seconds, 0);
		return ts;
	}

	public static TimeSpan opCall(int days, int hours, int minutes, int seconds)
	{
		TimeSpan ts;
		ts.m_ticks = calculateTicks (days, hours, minutes, seconds, 0);
		return ts;
	}

	public static TimeSpan opCall(int days, int hours, int minutes, int seconds, int milliseconds)
	{
		TimeSpan ts;
		ts.m_ticks = calculateTicks (days, hours, minutes, seconds, milliseconds);
		return ts;
	}

	private static long calculateTicks (int days, int hours, int minutes, int seconds, int milliseconds)
	{
		// there's no overflow checks for hours, minutes, ...
		// so big hours/minutes values can overflow at some point and change expected values
		int hrssec = (hours * 3600); // break point at (Int32.MaxValue - 596523)
		int minsec = (minutes * 60);
		long t = (cast(long)(hrssec + minsec + seconds) * 1000L + cast(long)milliseconds);
		t *= 10000;

		bool overflow = false;
		// days is problematic because it can overflow but that overflow can be 
		// "legal" (i.e. temporary) (e.g. if other parameters are negative) or 
		// illegal (e.g. sign change).
		if (days > 0) {
			long td = TicksPerDay * days;
			if (t < 0) {
				long ticks = t;
				t += td;
				// positive days -> total ticks should be lower
				overflow = (ticks > t);
			}
			else {
				t += td;
				// positive + positive != negative result
				overflow = (t < 0);
			}
		}
		else if (days < 0) {
			long td = TicksPerDay * days;
			if (t <= 0) {
				t += td;
				// negative + negative != positive result
				overflow = (t > 0);
			}
			else {
				long ticks = t;
				t += td;
				// negative days -> total ticks should be lower
				overflow = (t > ticks);
			}
		}

		if (overflow)
			throw new ArgumentOutOfRangeException ("The timespan is too big or too small.");

		return t;
	}

	public int days() 
	{
		return cast(int) (m_ticks / TicksPerDay);
	}

	public int hours()
	{
		return cast(int) (m_ticks % TicksPerDay / TicksPerHour);
	}

	public int milliseconds()
	{
		return cast(int) (m_ticks % TicksPerSecond / TicksPerMillisecond);
	}

	public int minutes() {
		return cast(int) (m_ticks % TicksPerHour / TicksPerMinute);
	}

	public int seconds() {
		return cast(int) (m_ticks % TicksPerMinute / TicksPerSecond);
	}

	public long ticks()
	{
			return m_ticks;
	}

	public double totalDays()
	{
		return cast(double) m_ticks / TicksPerDay;
	}

	public double totalHours() 
	{
		return cast(double) m_ticks / TicksPerHour;
	}

	public double totalMilliseconds()
	{
		return cast(double) m_ticks  / TicksPerMillisecond;
	}

	public double totalMinutes()
	{
		return cast(double) m_ticks / TicksPerMinute;
	}

	public double totalSeconds() {
		return cast(double) m_ticks / TicksPerSecond;
	}



	public TimeSpan duration()
	{
		TimeSpan ret = TimeSpan (abs (m_ticks));
		return ret;
	}

	public static bool equals (TimeSpan t1, TimeSpan t2)
	{
		return t1.m_ticks == t2.m_ticks;
	}

	public static TimeSpan fromDays (double value)
	{
		return from (value, TicksPerDay);
	}

	public static TimeSpan fromHours (double value)
	{
		return from (value, TicksPerHour);
	}

	public static TimeSpan fromMinutes (double value)
	{
		return from (value, TicksPerMinute);
	}

	public static TimeSpan fromSeconds (double value)
	{
		return from (value, TicksPerSecond);
	}

	public static TimeSpan fromMilliseconds (double value)
	{
		return from (value, TicksPerMillisecond);
	}

	private static TimeSpan from (double value, long tickMultiplicator)
	in {
		assert(value != double.nan);
		assert(value < max.ticks && value > min.ticks);
	}
	body {
		value = (value * (tickMultiplicator / TicksPerMillisecond));
		long v = cast(long)round(value);
		return TimeSpan (v * TicksPerMillisecond);
	}

	public static TimeSpan fromTicks (long value) {
		return TimeSpan (value);
	}

	public TimeSpan opAssign (long value) {
		this.m_ticks = value;
		return *this;
	}

	public hash_t toHash()
	{
		static if(hash_t.sizeof == m_ticks.sizeof)
		{
			return cast(hash_t)m_ticks;
		}
		//如果是64位机器就直接返回

		return cast(hash_t)(m_ticks & 0xffffffff) ^ cast(hash_t)(m_ticks >> 32);
	}

	public TimeSpan opNeg()
	in
	{
		assert(m_ticks != min.ticks);
	}
	body
	{
		return TimeSpan (-m_ticks);
	}

	public TimeSpan opPos()
	in
	{
		assert(m_ticks != max.ticks);
	}
	body
	{
		return TimeSpan(+m_ticks);
	}

	public TimeSpan opAdd(TimeSpan rhs) {
		return TimeSpan(ticks + rhs.ticks);
	}

	public TimeSpan opAddAssign(TimeSpan rhs) {
		m_ticks += rhs.ticks;
		return *this;
	}

	public TimeSpan opSub(TimeSpan rhs) {
		return TimeSpan(ticks - rhs.ticks);
	}

	public TimeSpan opSubAssign(TimeSpan rhs) {
		m_ticks -= rhs.ticks;
		return *this;
	}

	public int opCmp (TimeSpan rhs)
	{
		if (ticks > rhs.ticks) return 1;
		if (ticks < rhs.ticks) return -1;
		return 0;
	}

	public bool opEquals (TimeSpan rhs)
	{
		return rhs.ticks == ticks;
	}


	public char[] toString ()
	{
		char[32] result;
		size_t count = 0;

		void sink(char c) {
			result[count++] = c;
		}

		if (ticks < 0) sink('-');

		if (days != 0) {
			Integer.getChars!(int)(days, &sink);
			sink('.');
		}

		Formatter.format(&sink, "{0:D2}:{1:D2}:{2:D2}",abs(hours), 
				abs(minutes), abs(seconds));

		int fractional =  cast(int)abs (ticks % TicksPerSecond);
		if (fractional != 0) {
			sink('.');
			Integer.getChars!(int)(fractional, &sink, 7);
		}

		return result[0..count].dup;
	}


}

////////////////////////////////////////////////////////////////////////////////


public struct DateTime
{
	//两个成员
	private TimeSpan m_ticks;

	private const int dp400 = 146097;
	private const int dp100 = 36524;
	private const int dp4 = 1461;

	// w32 file time starts counting from 1/1/1601 00:00 GMT
	// which is the constant ticks from the .NET epoch
	private const long W32FileEpoch = 504911232000000000L;

	private const long MAX_VALUE_TICKS = 3155378975400000000L;
	// -- Microsoft .NET has this value.
	//private const long MAX_VALUE_TICKS = 3155378975999999999L;

	//
	// The UnixEpoch, it begins on Jan 1, 1970 at 0:0:0, expressed
	// in Ticks
	//
	package const long UnixEpoch = 621355968000000000L;

	// for OLE Automation dates
	private const long ticks18991230 = 599264352000000000L;
	private const double OAMinValue = -657435.0;
	private const double OAMaxValue = 2958466.0;

	//与 TimeSpan 的 min/max 相同
	public const DateTime max =  {m_ticks: TimeSpan(MAX_VALUE_TICKS)};
	public const DateTime min =  { m_ticks: TimeSpan(0) };


	/+
	private const char[][] commonFormats = [
		// For compatibility with MS's CLR, this format (which
		// doesn't have a one-letter equivalent) is parsed
		// too. It's important because it's used in XML
		// serialization.

		// Note that those format should be tried only for
		// invalid patterns; 

		// FIXME: SOME OF those patterns looks tried against 
		// the current culture, since some patterns fail in 
		// some culture.

		"yyyy-MM-dd",
		"yyyy-MM-ddTHH:mm:sszzz",
		"yyyy-MM-ddTHH:mm:ss.fffffff",
		"yyyy-MM-ddTHH:mm:ss.fffffffzzz",
		// bug #78618
		"yyyy-M-d H:m:s.fffffff",
		// UTC / allow any separator
		"yyyy/MM/ddTHH:mm:ssZ",
		"yyyy/M/dZ",
		// bug #58938
		"yyyy/M/d HH:mm:ss",
		// bug #47720
		"yyyy/MM/dd HH:mm:ss 'GMT'",
		// bug #53023
		"MM/dd/yyyy",
		// Close to RFC1123, but without 'GMT'
		"ddd, d MMM yyyy HH:mm:ss",
		// use UTC ('Z'; not literal "'Z'")
		// FIXME: 1078(af-ZA) and 1079(ka-GE) reject it
		"yyyy/MM/dd HH':'mm':'ssZ", 

		// bug #60912
		"M/d/yyyy HH':'mm':'ss tt",
		"H':'mm':'ss tt",
		// another funky COM dependent one
		"dd-MMM-yy",

		// DayOfTheWeek, dd full_month_name yyyy
		// FIXME: 1054(th-TH) rejects them
		"dddd, dd MMMM yyyy",
		"dddd, dd MMMM yyyy HH:mm",
		"dddd, dd MMMM yyyy HH:mm:ss",

		"yyyy MMMM",
		// DayOfTheWeek, dd yyyy. This works for every locales.
		"MMMM dd, yyyy",

		// X509Certificate pattern is accepted by Parse() *in every culture*
		"yyyyMMddHHmmssZ",
		// In Parse() the 'r' equivalent pattern is first parsed as universal time
		"ddd, dd MMM yyyy HH':'mm':'ss 'GMT'",

		// Additionally there seems language-specific format
		// patterns that however works in all language
		// environment.
		// For example, the pattern below is for Japanese.
		"yyyy'\u5E74'MM'\u6708'dd'\u65E5' HH'\u6642'mm'\u5206'ss'\u79D2'",

		// This one is parsed for all cultures
		"HH':'mm tt MM/dd/yyyy",

		/*
		// Full date and time
		"F", "G", "r", "s", "u", "U",
		// Full date and time, but no seconds
		"f", "g",
		// Only date
		"d", "D",
		// Only time
		"T", "t",
		// Only date, but no year
		"m",
		// Only date, but no day
		"y" 
		 */
	];+/

	private enum Which 
	{
		Day,
		DayYear,
		Month,
		Year
	};

	private static final int[13] DaysPerMonth = [ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];	
	private static final int[13] DaysMonthLeap = [ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];	

	private static int absoluteDays (int year, int month, int day)
	{
		int temp = 0, m=1 ;
		int[] days = (isLeapYear(year) ? DaysMonthLeap  : DaysPerMonth);

		while (m < month)
			temp += days[m++];
		return ((day-1) + temp + (365* (year-1)) + ((year-1)/4) - ((year-1)/100) + ((year-1)/400));
	}

	private int fromTicks(Which what)
	{
		int num400, num100, num4, numyears; 
		int M =1;

		int[] days = DaysPerMonth;
		int totaldays = this.m_ticks.days;

		num400 = (totaldays / dp400);
		totaldays -=  num400 * dp400;

		num100 = (totaldays / dp100);
		if (num100 == 4)   // leap
			num100 = 3;
		totaldays -= (num100 * dp100);

		num4 = totaldays / dp4;
		totaldays -= (num4 * dp4);

		numyears = totaldays / 365 ;

		if (numyears == 4)  //leap
			numyears =3 ;
		if (what == Which.Year )
			return num400*400 + num100*100 + num4*4 + numyears + 1;

		totaldays -= (numyears * 365) ;
		if (what == Which.DayYear )
			return totaldays + 1;

		if  ((numyears==3) && ((num100 == 3) || !(num4 == 24)) ) //31 dec leapyear
			days = DaysMonthLeap;

		while (totaldays >= days[M])
			totaldays -= days[M++];

		if (what == Which.Month )
			return M;

		return totaldays +1; 
	}


	// Constructors

	public static DateTime opCall (long nticks)
	{
		if (nticks < min.ticks || nticks > max.ticks) {
			throw new ArgumentOutOfRangeException ("Ticks is out of Range");
		}
		DateTime ret;
		ret.m_ticks = TimeSpan (nticks);
		return ret;
	}

	public static DateTime opCall (bool check, TimeSpan value)
	{
		if (check && (value.ticks < this.min.ticks || value.ticks > this.max.ticks))
			throw new ArgumentOutOfRangeException ("Argument value is out of range");

		DateTime ret = void;
		ret.m_ticks = value;
		return ret;
	}

	public static DateTime opCall (int year_, int month_, int day_)
	{
		return DateTime(year_, month_, day_, 0, 0, 0, 0);
	}

	public static DateTime opCall (int year_, int month_, int day_, int hour_, int minute_, int second_)
	{
		return DateTime(year_, month_, day_, hour_, minute_, second_, 0);
	}

	public static DateTime opCall (int year, int month, int day, 
			int hour, int minute, int second, int millisecond)
	{
		if ( year < 1 || year > 9999 || 
				month < 1 || month >12  ||
				day < 1 || day > daysInMonth(year, month) ||
				hour < 0 || hour > 23 ||
				minute < 0 || minute > 59 ||
				second < 0 || second > 59 ||
				millisecond < 0 || millisecond > 999)
			throw new ArgumentOutOfRangeException (
					"Parameters describe an unrepresentable DateTime.");
		
		DateTime ret;
		ret.m_ticks = TimeSpan (absoluteDays(year, month, day), hour, minute, second, millisecond);
		return ret;
	}

	/* Properties  */

	public DateTime date() 
	{
		DateTime ret = DateTime (this.year, this.month, this.day);
		return ret;
	}

	public int month() {
		return fromTicks(Which.Month); 
	}

	public int day() {
		return fromTicks(Which.Day); 
	}

	public DayOfWeek dayOfWeek() { 
		return ( cast(DayOfWeek)((m_ticks.days + 1) % 7) ); 
	}

	public int dayOfYear() {
		return fromTicks(Which.DayYear); 
	}

	public TimeSpan timeOfDay() {
		return TimeSpan(m_ticks.ticks % TimeSpan.TicksPerDay );
	}

	public int hour() {
		return m_ticks.hours;
	}

	public int minute() {
		return m_ticks.minutes;
	}

	public int second() {
		return m_ticks.seconds;
	}

	public int millisecond() {
		return m_ticks.milliseconds;
	}

	//
	// To reduce the time consumed by DateTime.Now, we keep
	// the difference to map the system time into a local
	// time into `to_local_time_span', we record the timestamp
	// for this in `last_now'
	//
	//static object to_local_time_span_object;
	public static DateTime now() 
	{
		assert(false);
		long n = getUtcNow ();
		return DateTime(n);
	}

	public long ticks()
	{ 
		return m_ticks.ticks;
	}

	public static DateTime today() 
	{
		DateTime nowTime = now;
		DateTime today = DateTime (nowTime.year, nowTime.month, nowTime.day);
		return today;
	}

	public static DateTime utcNow() {
		return DateTime (getUtcNow());
	}

	public int year() {
		return fromTicks(Which.Year); 
	}

	/* methods */

	public DateTime addDays (double d)
	{
		return addMilliseconds (round (d * 86400000));
	}

	public DateTime addTicks (long t)
	{
		if ((t + m_ticks.ticks) > MAX_VALUE_TICKS || (t + m_ticks.ticks) < 0) {
			throw new ArgumentOutOfRangeException("");
		}
		DateTime ret = DateTime (t + m_ticks.ticks);
		return ret;
	}

	public DateTime addHours (double h) {
		return addMilliseconds (h * 3600000);
	}

	public DateTime addMilliseconds (double ms)
	{
		if (	(ms * TimeSpan.TicksPerMillisecond) > long.max ||
				(ms * TimeSpan.TicksPerMillisecond) < long.min) 
		{
			throw new ArgumentOutOfRangeException("");
		}
		long msticks = cast(long) (ms * TimeSpan.TicksPerMillisecond);

		return addTicks (msticks);
	}

	// required to match MS implementation for OADate (OLE Automation)
	private DateTime addRoundedMilliseconds (double ms)
	{
		if (	(ms * TimeSpan.TicksPerMillisecond) > long.max ||
				(ms * TimeSpan.TicksPerMillisecond) < long.min) {
			throw new ArgumentOutOfRangeException ("");
		}
		long msticks = cast(long) (ms += ms > 0 ? 0.5 : -0.5) * TimeSpan.TicksPerMillisecond;

		return addTicks (msticks);
	}

	public DateTime addMinutes (double m) {
		return addMilliseconds (m * 60000);
	}

	public DateTime addMonths (int m)
	{
		int day_, month_, year_,  maxday_ ;
		DateTime temp ;

		day_ = this.day;
		month_ = this.month + (m % 12);
		year_ = this.year + m / 12 ;

		if (month_ < 1)
		{
			month_ = 12 + month_ ;
			year_ -- ;
		}
		else if (month_ > 12) 
		{
			month_ = month_ -12;
			year_ ++;
		}
		maxday_ = daysInMonth(year_, month_);
		if (day_ > maxday_)
			day_ = maxday_;

		temp = DateTime (year_, month_, day_);
		return  temp.opAdd (this.timeOfDay);
	}

	public DateTime addSeconds (double sec)
	{
		return addMilliseconds (sec * 1000);
	}

	public DateTime addYears (int y )
	{
		return addMonths(y * 12);
	}

	public int opCmp (DateTime rhs)
	{
		if (this.ticks < rhs.ticks) 
			return -1;
		else if (this.ticks > rhs.ticks) 
			return 1;
		else
			return 0;
	}

	//DateTime-TimeSpan=DateTime
	//DateTime-DateTime=TimeSpan
	//DateTime+TimeSpan=DateTime

	public DateTime opAdd (TimeSpan rhs) {
		return addTicks (rhs.ticks);
	}

	public DateTime opAddAssign(TimeSpan rhs) {
		*this = addTicks(rhs.ticks);
		return *this;
	}

	public DateTime opSub(TimeSpan rhs) {   
		DateTime ret;
		ret.m_ticks = this.m_ticks - rhs;
		return ret;
	}

	public TimeSpan opSub(DateTime rhs) {
		return TimeSpan(this.m_ticks.ticks) - rhs.m_ticks;
	}

	public DateTime opSubAssign(TimeSpan rhs) {
		this.m_ticks -= rhs;
		return *this;
	}

	public TimeSpan opSubAssign(DateTime rhs) {
		this.m_ticks -= rhs.m_ticks;
		return this.m_ticks;
	}

	public bool opEquals(DateTime rhs) {
		return rhs.ticks == this.ticks;
	}

	public bool isDaylightSavingTime () {
		assert(false);
		//return TimeZone.CurrentTimeZone.IsDaylightSavingTime (this);
	}

	public long toBinary () {
		return m_ticks.ticks;
	}

	public static DateTime fromBinary (long dateData) {
		return DateTime (dateData);
	}

	public static int daysInMonth (int y, int m) 
	in {
		assert(m >= 1 && m <= 12, "Argument month is out of range");
	}
	body {
		int[] days_ ;

		days_ = (isLeapYear(y) ? DaysMonthLeap  : DaysPerMonth);
		return days_[m];	
	}

	public static DateTime fromWindowsFileTimeUtc (long fileTime) 
	in {
		assert(fileTime >= 0, "fileTime < 0");
	}
	body{
		return DateTime (W32FileEpoch + fileTime);
	}

	public static DateTime fromWindowsFileTime (long fileTime) 
	{
		return fromWindowsFileTimeUtc(fileTime);
	}

	public static DateTime fromPosixTimeUtc(uint time) // time_t
	in {
		assert(time >= 0);
	}
	body {
		return DateTime (UnixEpoch + time * TimeSpan.TicksPerSecond);
	}

	public hash_t toHash () {
		return m_ticks.toHash();
	}

	public long toWindowsFileTime()
	{
		DateTime universalTime = toUniversalTime();

		if(universalTime.ticks < W32FileEpoch) 
			throw new ArgumentOutOfRangeException("File time is not valid");

		return universalTime.ticks - W32FileEpoch;
	}

	public long toWindowsFileTimeUtc()
	{
		if(ticks < W32FileEpoch) 
			throw new ArgumentOutOfRangeException("File time is not valid");

		return ticks - W32FileEpoch;
	}

	public uint toPosixTimeUtc()
	{
		DateTime universalTime = toUniversalTime();

		if(universalTime.ticks < UnixEpoch) 
			throw new ArgumentOutOfRangeException("Time is not valid");

		return cast(uint)((universalTime.ticks - UnixEpoch) / TimeSpan.TicksPerSecond);
	}

	public static bool isLeapYear (int year_) 
	in {
		assert(year_ > 0);
	}
	body {
		return  ( (year_ % 4 == 0 && year_ % 100 != 0) || year_ % 400 == 0) ;
	}

	public DateTime toLocalTime ()
	{
		//TODO: return TimeZone.currentTimeZone.toLocalTime (this);
		assert(false, "Not implemented");
		return DateTime(0);
	}

	public DateTime toUniversalTime()
	{
		assert(false);
		return DateTime(0);
		//return TimeZone.currentTimeZone.toUniversalTime (this);
	}


version(Windows) {

	package static long getUtcNow()
	{
		FILETIME ft = void;
		GetSystemTimeAsFileTime(&ft);
		return fromWindowsFileTime(fileTimeToLong(ft)).ticks;
	}
} //version(windows)


version(Posix) {

	package static long getUtcNow()
	{
		timeval t = void;

		if(!gettimeofday(&t, null) == 0)
			throw new Exception("Unknown error"); 

		long ntick = TimeSpan.TicksPerSecond * t.tv_sec;
		ntick += TimeSpan.TicksPerMillisecond * t.tv_usec;
		ntick += UnixEpoch;

		return ntick;
	}
}//version(Posix)



}

